// Copyright (c) 2010 Omer Goshen
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#include "MudBath.h"
#include "App.h"
#include <OgreConfigFile.h>

#include "gamestates/GameState.h"
#include "Timer.h"

using namespace std;
using namespace Ogre;
using namespace BtOgre;

App* App::ms_instance = 0;
const Ogre::String App::version = "0.1";

/** Globals **/
float timeMulti = 1;
int NumScreenShots = 0;
bool fixedCam = false;


void foo(void)
{
	std::cout << "foo() was called at " << ::Timer::getInstance()->getTime() << std::endl;
//	::Timer::getInstance()->delay(&foo, 500);
}

/**
 * \brief
 * \param state
 */
void App::start(GameState* state) throw(Ogre::Exception)
{
	try
		{
//	Ogre::LogManager* logger = new Ogre::LogManager();
//	logger->createLog("Ogre.log", true, false, false);

			mTimer = new Ogre::Timer();
			mRoot = new Root();

//	Log* log = Ogre::LogManager::getSingleton().getDefaultLog();
//	log->setDebugOutputEnabled(false);
//	log->setTimeStampEnabled(true);

			// Load the default config file
			mCF.load("default.cfg");

			setupResources();

			if (!configure()) return;

			ResourceGroupManager::getSingleton().initialiseAllResourceGroups();


			// OIS
			initInput();

			WindowEventUtilities::addWindowEventListener(mWindow, this);
			mRoot->addFrameListener(this);

			mDebugOverlay = OverlayManager::getSingleton().getByName("Debug");
			bool showDebug = StringConverter::parseBool(mCF.getSetting("ShowDebug", "General", "true"));
			if(showDebug)
				mDebugOverlay->show();

			initDeferredShading();

			// SceneManager
			mSceneMgr = mRoot->createSceneManager(ST_GENERIC,StringUtil::BLANK);

			// Bullet
			initBullet();

			// LUA
			initLua();

			// Console
			console = Console::getSingletonPtr();
			console->init(mRoot);

			// Interpreters
			LuaInterpreter* luai = new LuaInterpreter(L);
			ScriptManager::getSingletonPtr()->interps.push_back(dynamic_cast<Interpreter*>(luai));
			luaL_dofile( L, "consolePrint.lua" );

			JavaScriptInterpreter* jsi = new JavaScriptInterpreter();
			JSGlue::glue(jsi->mContext);
			ScriptManager::getSingletonPtr()->interps.push_back(dynamic_cast<Interpreter*>(jsi));
			js_ctx = jsi->mContext;


			// Get console interpreter from config file, default to lua
			Ogre::String interpName = mCF.getSetting("ConsoleInterpreter", "General", "lua");

			if(interpName=="lua")
				console->setInterpreter(luai);
			else if(interpName=="javascript")
				console->setInterpreter(jsi);

			// ComponentFactory
			ComponentFactory::getSingleton().Register("TransformComponent", new ComponentCreator<TransformComponent>());
			ComponentFactory::getSingleton().Register("RenderComponent", new ComponentCreator<RenderComponent>());
			ComponentFactory::getSingleton().Register("AnimationComponent", new ComponentCreator<AnimationComponent>());
			ComponentFactory::getSingleton().Register("ScriptComponent", new ComponentCreator<ScriptComponent>());

			// EventManager
//			EventManager::getInstance().addEventDispatcher(this);

			mWindow->setAutoUpdated(false);
			mRoot->clearEventTimes();


			cout << "--------------------- init.lua ----------------------" << endl;
			luaL_dofile( L, "init.lua" );
			if(interpName=="lua")
				{
					console->print( console->getInterpreter()->getOutput() );
					console->getInterpreter()->clearOutput();
				}

			// push the game state
			if(state)
				changeState(state);

		}
	catch(Ogre::Exception& e)
		{
			throw e;
		}

	::Timer::getInstance()->delay(&foo, 1000);

	::Object o;
	o.addProperty(PropertyDeserializer::fromXmlString("<Property name=\"foo\" type=\"string\" value=\"bar\"/>"));
	o.addProperty(PropertyDeserializer::fromXmlString("<Property name=\"a\" type=\"int\" value=\"1\"/>"));
	o.addProperty(PropertyDeserializer::fromXmlString("<Property name=\"b\" type=\"float\" value=\"3.14\"/>"));
	o.addProperty(PropertyDeserializer::fromXmlString("<Property name=\"v\" type=\"Vector3\" value=\"1 2 3\"/>"));
	TRACE(o.getXmlString());

	cout << "+------------------------------+" << endl;
	cout << "|         Start Looping...     |" << endl;
	cout << "+------------------------------+" << endl;
	while(run()) {}
	shutdown();
}


void App::initLua()
{
	cout << "+------------------------------+" << endl;
	cout << "|         Binding Lua          |" << endl;
	cout << "+------------------------------+" << endl;

	L = lua_open();

	luaL_openlibs(L);

//	luaopen_base(L);
//	luaopen_table(L);
//	luaopen_io(L);
//	luaopen_string(L);
//	luaopen_math(L);
//	luaopen_debug(L);

	LuaGlue::Register(L);

	cout << "+------------------------------+" << endl;
	cout << "|     Finished Binding Lua     |" << endl;
	cout << "+------------------------------+" << endl;
}


bool App::frameStarted(const FrameEvent& evt)
{
//	CEGUI::System::getSingleton().injectTimePulse(evt.timeSinceLastFrame);
//
//	if (mPhysicsActive)
//		{
//			phyWorld->stepSimulation(evt.timeSinceLastFrame * mTimerMultiplier, 10, mTimerMultiplier*mPhysicsFixedTimeStamp);
//			dbgdraw->step();
//		}

//	fire(new Event());
	onFrameStarted.emit();

	if(mStates.empty()) return !mQuit;

	return !mQuit && mStates.back()->frameStarted(evt);
}

bool App::frameEnded(const FrameEvent& evt)
{
	if(mStates.empty()) return !mQuit;

	return !mQuit && mStates.back()->frameEnded(evt);
}

bool App::frameRenderingQueued(const Ogre::FrameEvent& evt)
{
//	mInputMgr->capture();

//	return !mQuit && mStates.back()->frameRenderingQueued(evt);
	return !mQuit;
}


App::App() :
	mQuit(false),
	mRoot(0),
	mSceneMgr(0),
	mCamera(0),
	mViewport(0),
	mWindow(0),
	mSystem(0),
	mTimer(0),
	console(0),
	mInputMgr(0),
	mSoundMgr(0),
	L(0),
	phyWorld(0),
	mPhysicsActive(false),
	mDebugOverlay(0)
{
}


App::~App()
{
}


void App::shutdown()
{
	cout << "+------------------------------+" << endl;
	cout << "|        Shutting Down         |" << endl;
	cout << "+------------------------------+" << endl;

	try
		{
			while (!mStates.empty())
				{
					mStates.back()->exit();
					mStates.pop_back();
				}

			mRoot->removeFrameListener(this);
			mWindow->removeAllListeners();

//			delete mTimer;
//			delete phyWorld;
//			delete dbgdraw;
//			delete console;

			lua_close(L);
			//Py_Finalize();
			js_ctx.Dispose();

//			mSceneMgr->clearScene();
			delete mSceneMgr;

			MaterialManager::getSingleton().destroyAllResourcePools();
			OverlayManager::getSingleton().destroyAll();
		}
	catch(Ogre::Exception& e)
		{
			cout << e.getFullDescription() << endl;
		}
	catch(std::exception& e)
		{
			cout << e.what() << endl;
		}
	catch(...)
		{
			throw;
		}
	cout << "All done, bye." << endl;
}


/**
 * \brief
 */
void App::setupResources(void)
{
	// Load resource paths from config file
	ConfigFile cf;
	cf.load("resources.cfg");

	// Go through all sections & settings in the file
	ConfigFile::SectionIterator seci = cf.getSectionIterator();

	Ogre::String secName, typeName, archName;
	while (seci.hasMoreElements())
		{
			secName = seci.peekNextKey();
			ConfigFile::SettingsMultiMap *settings = seci.getNext();
			ConfigFile::SettingsMultiMap::iterator i;
			for (i = settings->begin(); i != settings->end(); ++i)
				{
					typeName = i->first;
					archName = i->second;
#if OGRE_PLATFORM == OGRE_PLATFORM_APPLE
					ResourceGroupManager::getSingleton().addResourceLocation(
					    String(macBundlePath() + "/" + archName), typeName, secName);
#else
					ResourceGroupManager::getSingleton().addResourceLocation(
					    archName, typeName, secName);
#endif
				}
		}
}

/**
 * \brief Load a dotScene xml file and create game objects
 * \param dotScene
 */
void App::loadSceneXml(const Ogre::String& dotScene)
{
	// Load scene xml
	LogManager::getSingletonPtr()->logMessage("--------------------- Parsing DotScene ---------------------");
	DotSceneLoader dsl;
	dsl.parseDotScene(dotScene, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, mSceneMgr);

	SceneNode* rootNode = mSceneMgr->getRootSceneNode();
	SceneNode::ChildNodeIterator iter = rootNode->getChildIterator();
	while (iter.hasMoreElements())
		{
			//! TODO: dynamic to static casts?
			try
				{
					SceneNode* node = dynamic_cast<SceneNode*>(iter.getNext());
					if(node==NULL || node->numAttachedObjects()==0) continue;

					Ogre::MovableObject* mo = node->getAttachedObject(0);

					Ogre::String moveableType = mo->getMovableType();
					Ogre::String nodeName = node->getName();

					cout << "--- " << mo->getName() << "[" << moveableType << "] ---" << endl;

					if (moveableType=="Camera")
						{
							mCamera = dynamic_cast<Camera*>(node->getAttachedObject(0));
							if(mCamera==NULL) continue;
							if(mViewport == NULL || mViewport->getCamera()==NULL)
								mViewport = mWindow->addViewport(mCamera);
							continue;
						}

					if (moveableType=="Light")
						{
							Light* light = static_cast<Light*>(node->getAttachedObject(0));
							GameLight* glight = new GameLight();
							glight->addComponent(new TransformComponent(glight, "TransformComponent", node));
							glight->mLight = light;
							glight->init();
							continue;
						}

					if (moveableType=="Entity")
						{
							Entity* ent = dynamic_cast<Entity*>(node->getAttachedObject(0));
							if(ent==NULL) continue;

//							GameObject* go = new GameObject();
							GameObject* go = new GameObject(nodeName);

							go->addComponent(new TransformComponent(go, "TransformComponent", node));
							go->addComponent(new RenderComponent(go, "RenderComponent", ent));

							if (dsl.getProperty(nodeName, "mass")=="" && dsl.getProperty(nodeName, "shape")=="") continue;

							Real mass = StringConverter::parseReal(dsl.getProperty(nodeName, "mass"));
							Ogre::String shapeStr = dsl.getProperty(nodeName, "shape");

							btCollisionShape* shape = ShapeResolver::getShape(shapeStr, ent);
							go->addComponent(new PhysicsComponent(phyWorld, go, mass, shape));

							continue;
						}
				}
			catch(Ogre::Exception& e)
				{
					std::cout << e.what() << std::endl;
					return;
				}
		}

}

/**
 * \brief Initialize the deferred shading system
 * \throw an Ogre::Exception if not supported on hardware
 */
void App::initDeferredShading() throw(Ogre::Exception)
{
	RenderSystem *rs = Root::getSingleton().getRenderSystem();
	const RenderSystemCapabilities* caps = rs->getCapabilities();
	if (!caps->hasCapability(RSC_VERTEX_PROGRAM) || !(caps->hasCapability(RSC_FRAGMENT_PROGRAM)))
		{
			OGRE_EXCEPT(Ogre::Exception::ERR_NOT_IMPLEMENTED, "Your card does not support vertex and fragment programs, so cannot "
			            "run this demo. Sorry!",
			            "DeferredShading::createScene");
		}
	if (caps->getNumMultiRenderTargets()<2)
		{
			OGRE_EXCEPT(Ogre::Exception::ERR_NOT_IMPLEMENTED, "Your card does not support at least two simultaneous render targets, so cannot "
			            "run this demo. Sorry!",
			            "DeferredShading::createScene");
		}
}


/**
 * \brief Initialize Bullet Physics
 */
void App::initBullet()
{
	cout << "+------------------------------+" << endl;
	cout << "| Initializing Bullet Physics  |" << endl;
	cout << "+------------------------------+" << endl;

	LogManager::getSingletonPtr()->logMessage("*** Initializing Bullet Physics ***");

	mTimerMultiplier = 1.f;
	bool phyactive = StringConverter::parseBool(mCF.getSetting("PhysicsActive", "Physics", "true"));
	mPhysicsActive = phyactive;

	mPhysicsFixedTimeStamp = StringConverter::parseReal(mCF.getSetting("TimeStep", "Physics", "0.016666667"));
//	mPhysicsFixedTimeStamp = 1.f / 60.f;

	try
		{
			// Build the broadphase
			int maxProxies = 1024;
			btVector3 worldAabbMin(-10000,-10000,-10000);
			btVector3 worldAabbMax(10000,10000,10000);
			btAxisSweep3* broadphase = new btAxisSweep3(worldAabbMin,worldAabbMax,maxProxies);

			// Set up the collision configuration and dispatcher
			btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration();
			btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration);

			// The actual physics solver
			btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver;

			// The physics world
			phyWorld = new btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration);
			phyWorld->setGravity(btVector3(0,-9.8,0));

			// Debug drawing...
			dbgdraw = new BtOgre::DebugDrawer(mSceneMgr->getRootSceneNode(), phyWorld);
			bool showdbg = StringConverter::parseBool(mCF.getSetting("ShowDebug", "Physics", "true"));
			dbgdraw->setDebugMode(showdbg);
			phyWorld->setDebugDrawer(dbgdraw);
		}
	catch(...)
		{
		}
}

/**
 * KeyPress Handler
 * @param e KeyEvent from OIS
 * @return true to continue rendering
 */
bool App::keyPressed( const OIS::KeyEvent &e )
{
	if( e.key == OIS::KC_SYSRQ )
		{
			mWindow->writeContentsToFile("ScreenShot"+Ogre::StringConverter::toString(NumScreenShots++)+".png");
			return true;
		}

	if ( e.key == OIS::KC_GRAVE )
		{
			console->setVisible( !console->isVisible() );
			return true;
		}

	if (console->isVisible() )
		{
			console->isModifierDown = InputManager::getSingletonPtr()->getKeyboard()->isModifierDown(OIS::Keyboard::Shift);
			console->injectKeyPress(e);
			return true;
		}

	mQuit = e.key == OIS::KC_ESCAPE;
	if(mStates.empty()) return !mQuit;

	// Call keyPressed of current state
	mStates.back()->keyPressed( e );

	return true;
}

/**
 * \brief
 * \param e
 * \return
 */
bool App::keyReleased( const OIS::KeyEvent &e )
{
	if ( Console::getSingletonPtr()->isVisible() ) return true;

	mQuit = e.key == OIS::KC_ESCAPE;
	if(mStates.empty()) return !mQuit;


	// Call keyReleased of current state
	mStates.back()->keyReleased( e );

	return true;
}

/**
 * \brief
 * \param e
 * \return
 */
bool App::mouseMoved( const OIS::MouseEvent &e )
{
	if(mStates.empty()) return true;

	// Call mouseMoved of current state
	mStates.back()->mouseMoved( e );

	return true;
}

/**
 * \brief
 * \param e
 * \param id
 * \return
 */
bool App::mousePressed( const OIS::MouseEvent &e, OIS::MouseButtonID id )
{
	if(mStates.empty()) return true;

	// Call mousePressed of current state
	mStates.back()->mousePressed( e, id );

	return true;
}

/**
 * \brief
 * \param e
 * \param id
 * \return
 */
bool App::mouseReleased( const OIS::MouseEvent &e, OIS::MouseButtonID id )
{
	if(mStates.empty()) return true;

	// Call mouseReleased of current state
	mStates.back()->mouseReleased( e, id );

	return true;
}

/**
 * \brief Change to a given state
 * \param state
 */
void App::changeState(GameState* state)
{
	// cleanup the current state
	if ( !mStates.empty() )
		{
			mStates.back()->exit();
			mStates.pop_back();
		}

	// store and init the new state
	mStates.push_back(state);
	mStates.back()->enter();
}


/**
 * \brief
 * \param state
 */
void App::pushState(GameState* state)
{
	// pause current state
	if ( !mStates.empty() )
		{
			mStates.back()->pause();
		}

	// store and init the new state
	mStates.push_back(state);
	mStates.back()->enter();
}

/**
 * \brief
 */
void App::popState()
{
	// cleanup the current state
	if ( !mStates.empty() )
		{
			mStates.back()->exit();
			mStates.pop_back();
		}

	// resume previous state
	if ( !mStates.empty() )
		{
			mStates.back()->resume();
		}
}

bool App::configure(void)
{
	// load config settings from ogre.cfg
	if (!mRoot->restoreConfig())
		{
			// if there is no config file, show the configuration dialog
			if (!mRoot->showConfigDialog())
				{
					return false;
				}
		}

	// initialise and create a default rendering window
	mWindow = mRoot->initialise(true, "Ogre");

	return true;
}


/**
 * \brief Adjust mouse clipping area
 * \param rw
 */
void App::windowResized(RenderWindow* rw)
{
	unsigned int width, height, depth;
	int left, top;
	rw->getMetrics(width, height, depth, left, top);
	mInputMgr->setWindowExtents(width, height);
}


bool App::run()
{
	::Timer::getInstance()->processQueue();

	updateStatsOverlay();

	mInputMgr->capture();
	if(mWindow->isClosed()) return !mQuit;
	Ogre::WindowEventUtilities::messagePump();

	unsigned long t = mTimer->getMilliseconds();
	mFrameTime = t - mLastUpdateTime;
	mLastUpdateTime = t;
	mFrameStats.total = mFrameTime;

#define NOW(x) 	x = mTimer->getMilliseconds();
#define DT(x) (mTimer->getMilliseconds() - x)

	NOW(t);
//	float dt = mFrameTime * 0.001f;
//	CEGUI::System::getSingleton().injectTimePulse(dt);
//	CEGUI::System::getSingleton().renderGUI();
//	mFrameStats.gui = mTimer->getMilliseconds() - t;
	mFrameStats.gui = DT(t);

//	t = mTimer->getMilliseconds();
	NOW(t);
	stepScripts();
	mFrameStats.script = DT(t);
//	mFrameStats.script = mTimer->getMilliseconds() - t;

//	t = mTimer->getMilliseconds();
	NOW(t);
	stepPhysics();
//	mFrameStats.physics = mTimer->getMilliseconds() - t;
	mFrameStats.physics = DT(t);


//	t = mTimer->getMilliseconds();
	NOW(t);
	if (mRoot->renderOneFrame()==false) return !mQuit;

	mWindow->swapBuffers(false);
	mWindow->update(false);

//	mFrameStats.render = mTimer->getMilliseconds() - t;
	mFrameStats.render = DT(t);

	return !mQuit;
}

void App::stepPhysics()
{
	if (!mPhysicsActive) return;

	phyWorld->stepSimulation(mFrameTime*0.001f * mTimerMultiplier, 10, mPhysicsFixedTimeStamp );
	//			phyWorld->stepSimulation(mFrameTime * 0.001f * mTimerMultiplier, 10);

	dbgdraw->step();
}

void App::stepScripts()
{
	GameObject::ObjectIterator it = GameObject::getObjectsIterator();
	while(it.hasMoreElements())
		{
			GameObject* go = it.getNext();
			if(go==NULL || !go->hasProperty("actions")) continue;

			::Object actions = go->getProperty< ::Object >("actions");
			if(actions.isNull() || !actions.hasProperty("update")) continue;

			int idx = actions.getProperty<int>("update");

			lua_rawgeti(L, LUA_REGISTRYINDEX, idx);


//			Wrapper<LuaBinding::GameObject>::Instance(L, go);
			lua_pushstring(L, go->getName().c_str());


			if (lua_pcall(L, 1, 0, 0) != 0)
				luaL_error(L, "error running function: %s",
				           lua_tostring(L, -1));
		}
}

void App::updateStatsOverlay()
{
	const unsigned long updateInterval = 500;

	if(!mDebugOverlay || !mDebugOverlay->isVisible()) return;

	mFrameStatsUpdateTime += mFrameTime;
	if(mFrameStatsUpdateTime < updateInterval) return;
	mFrameStatsUpdateTime = 0;

	std::stringstream ss;
	OverlayContainer* panel = mDebugOverlay->getChild("StatPanel");

	TextAreaOverlayElement* txt = static_cast<TextAreaOverlayElement*>(panel->getChild("TriBatch"));
	ss << mWindow->getTriangleCount() << " | " << mWindow->getBatchCount();
	txt->setCaption(ss.str());

	txt = static_cast<TextAreaOverlayElement*>(panel->getChild("Fps"));
	ss.str( "" );
	ss << mWindow->getLastFPS() << " | ";
	ss << mWindow->getWorstFPS() << " | ";
	ss << mWindow->getAverageFPS() << " | ";
	ss << mWindow->getBestFPS();
	txt->setCaption(ss.str());

	txt = static_cast<TextAreaOverlayElement*>(panel->getChild("FrameTime"));
	ss.str( "" );
//	ss << mFrameTime << " | ";
	ss << mFrameStats.render << " | ";
	ss << mWindow->getBestFrameTime() << " | ";
	ss << mWindow->getWorstFrameTime() << " [ms]";
	txt->setCaption(ss.str());


#define FORMAT(x) setiosflags(ios::right) << setfill(' ') << setw(3) << std::fixed << std::setprecision(0) << ((float(x) / float(mFrameStats.total)) * 100.f) << "% "

	txt = static_cast<TextAreaOverlayElement*>(panel->getChild("Percents"));
	ss.str( "" );
	ss << "Render: "  << FORMAT(mFrameStats.render);
	ss << "Script: "  << FORMAT(mFrameStats.script);
	ss << "Gui: "     << FORMAT(mFrameStats.gui);
	ss << "Physics: " << FORMAT(mFrameStats.physics);
	txt->setCaption(ss.str());
}

void App::initInput()
{
	mInputMgr = InputManager::getSingletonPtr();
	bool exclusive = StringConverter::parseBool(mCF.getSetting("ExclusiveInput", "General", "true"));
	mInputMgr->initialise(mWindow, exclusive);

	mInputMgr->addKeyListener( this, "OgreApplication" );
	mInputMgr->addMouseListener( this, "OgreApplication" );
}
